/************************************************
 * Author       :   XXY
 * Blog         :   https://www.cnblogs.com/jzyl
 * Gitee        :   https://gitee.com/xiaobaichong
 * Email        :   1047185209@QQ.com
 * FileName     :   CurveUtility.cs
 * CreateData   :   2023/12/19 22:43:33
 * UnityVersion :   2021.3.30f1c1
 * Description  :   曲线工具
************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Main
{
    public static class CurveUtility
    {
        #region 贝塞尔曲线
        /// <summary>
        /// 计算4点二维维贝塞尔样条曲线的值
        /// </summary>
        /// <param name="startPosition">起点</param>
        /// <param name="controlPosition">控制点</param>
        /// <param name="endPosition">终点</param>
        /// <param name="t">沿着样条曲线的距离（0…1）</param>
        /// <returns></returns>
        public static Vector3 Bezier3Point(Vector3 startPosition, Vector3 controlPosition, Vector3 endPosition, float t)
        {
            return (1 - t) * (1 - t) * startPosition + 2 * t * (1 - t) * controlPosition + t * t * endPosition;
        }
        /// <summary>
        /// 计算4点二维贝塞尔样条曲线的切线
        /// </summary>
        /// <param name="startPosition">起点</param>
        /// <param name="controlPosition">控制点</param>
        /// <param name="endPosition">终点</param>
        /// <param name="t">沿着样条曲线的距离（0…1）</param>
        /// <returns></returns>
        public static Vector3 Bezier3Tangent(Vector3 startPosition, Vector3 controlPosition, Vector3 endPosition, float t)
        {
            return 2 * (1 - t) * (controlPosition - startPosition) + 2 * t * (endPosition - controlPosition);
        }
        /// <summary>
        /// 计算4点三维贝塞尔样条曲线的值
        /// </summary>
        /// <param name="startPosition">起点</param>
        /// <param name="endPosition">终点</param>
        /// <param name="startTangent">起点切线</param>
        /// <param name="endTangent">终点切线</param>
        /// <param name="t">沿着样条曲线的距离（0…1）</param>
        /// <returns>t处的样条曲线位置</returns>
        public static Vector3 Bezier4Point(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float t)
        {
            return (1 - t) * (1 - t) * (1 - t) * startPosition + 3 * (1 - t) * (1 - t) * t * startTangent + 3 * (1 - t) * t * t * endTangent + t * t * t * endPosition;
        }
        /// <summary>
        /// 计算4点三维贝塞尔样条曲线的切线
        /// </summary>
        /// <param name="startPosition">起点</param>
        /// <param name="endPosition">终点</param>
        /// <param name="startTangent">起点切线</param>
        /// <param name="endTangent">终点切线</param>
        /// <param name="t">沿着样条曲线的距离（0…1）</param>
        /// <returns>t处的样条曲线切线</returns>
        public static Vector3 Bezier4Tangent(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float t)
        {
            Vector3 tangent = 3 * (1 - t) * (1 - t) * (startTangent - startPosition) + 6 * (1 - t) * t * (endTangent - startTangent) + 3 * t * t * (endPosition - endTangent);
            return tangent.normalized;
        }
        #endregion
        #region  抛物线
        /// <summary>
        /// 计算抛物线点
        /// </summary>
        /// <param name="start">起点</param>
        /// <param name="time">时间点</param>
        /// <param name="dir">抛射方向</param>
        /// <param name="speed">抛射速度</param>
        /// <returns></returns>
        public static Vector3 ParaCurve(Vector3 start, Vector3 dir, float speed, float time)
        {
            Vector3 velocity = dir.normalized * speed; // 计算抛射的速度向量
            Vector3 pos = start + velocity * time; // 计算抛射时间点的位置
            float gravity = -9.8f; // 重力加速度
            pos.y += 0.5f * gravity * time * time; // 根据重力加速度计算抛射高度
            return pos;
        }
        /// <summary>
        /// 计算抛物线方向（切线）
        /// </summary>
        /// <param name="start">起点</param>
        /// <param name="dir">抛射方向</param>
        /// <param name="speed">抛射速度</param>
        /// <param name="time">时间点</param>
        /// <returns></returns>
        public static Vector3 ParaCurveDir(Vector3 start, Vector3 dir, float speed, float time)
        {
            Vector3 tangent = ParaCurve(start, dir, speed, time) - ParaCurve(start, dir, speed, time - 0.0001f);
            return tangent.normalized;
        }
        #endregion
        #region Other
        /// <summary>
        /// 求一元线性回归方程: y~=ax+b
        /// </summary>
        /// <param name="points">离散点</param>
        /// <returns>(a,b)</returns>
        public static Vector2 LinearRegression(Vector2[] points)
        {
            float averageX = 0;
            float averageY = 0;
            foreach (var item in points)
            {
                averageX += item.x;
                averageY += item.y;
            }
            averageX /= points.Length;
            averageY /= points.Length;
            float moSum = 0;
            float deSum = 0;
            foreach (var item in points)
            {
                moSum += (item.x - averageX) * (item.y - averageY);
                deSum += (item.x - averageX) * (item.x - averageX);
            }
            float a = moSum / deSum;
            float b = averageY - a * averageX;
            return new Vector2(a, b);
        }
        /// <summary>
        /// 过某些点作一个多边形，使这个多边形能把所有点都“包”起来。(Graham扫描法)
        /// </summary>
        /// <param name="points"></param>
        /// <returns></returns>
        public static Vector2[] ConvexHull(Vector2[] points)
        {
            Vector2 p0 = points[0];         // 纵坐标最小的点
            foreach (var item in points)
            {
                if (item.y < p0.y)
                {
                    p0 = item;
                }
            }
            // 各个点相对于 P0 的幅角 α ，按从小到大的顺序对各个点排序。
            // points 第一个和最后一个必是凸包上的点
            // 排序算法
            Func<Vector2, Vector2, int> comparer = (vecA, vecB) =>
            {
                float angleA = Vector2.Angle(vecA - p0, Vector2.right);
                float angleB = Vector2.Angle(vecB - p0, Vector2.right);
                if (angleA > angleB)
                {
                    return 1;
                }
                else if (angleA < angleB)
                {
                    return -1;
                }
                else
                {
                    return Vector2.Distance(vecA, p0) > Vector2.Distance(vecB, p0) ? 1 : -1;
                }
            };
            // 冒泡排序
            for (int i = 0; i < points.Length - 1; i++)
            {
                for (int j = 0; j < points.Length - 1 - i; j++)
                {
                    if (comparer(points[j], points[j + 1]) > 0)
                    {
                        Vector2 temp = points[j + 1];
                        points[j + 1] = points[j];
                        points[j] = temp;
                    }
                }
            }
            // 点c在ab向量的左边还是右边，为正在左边，为复负在右边
            Func<Vector2, Vector2, Vector2, float> IsClockwise = (a, b, c) => { return a.x * b.y + c.x * a.y + b.x * c.y - c.x * b.y - b.x * a.y - a.x * c.y; };
            Stack<Vector2> stack = new Stack<Vector2>();
            stack.Push(points[0]);
            stack.Push(points[1]);
            for (int i = 2; i < points.Length; i++)
            {
                Vector2 top = stack.Pop();
                while (stack.Count > 0 && IsClockwise(stack.Peek(), top, points[i]) < 0)
                {
                    top = stack.Pop();
                }
                stack.Push(top);
                stack.Push(points[i]);
            }
            return stack.ToArray();
        }
        /// <summary>
        /// 爱心形状
        /// </summary>
        /// <param name="t">范围：0-1</param>
        /// <returns>形状位置坐标点</returns>
        public static Vector2 HeartShape(float t)
        {
            t = Mathf.Clamp01(t) * 2 * Mathf.PI;
            float x = 16 * Mathf.Pow(Mathf.Sin(t), 3);
            float y = 13 * Mathf.Cos(t) - 5 * Mathf.Cos(2 * t) - 2 * Mathf.Cos(3 * t) - Mathf.Cos(4 * t);
            return new Vector2(x, y);
        }
        /// <summary>
        /// 圆形形状
        /// </summary>
        /// <param name="t">范围：0-1</param>
        /// <returns>形状位置坐标点</returns>
        public static Vector2 CircleShape(float t)
        {
            t = Mathf.Clamp01(t) * 2 * Mathf.PI;
            float x = Mathf.Sin(t);
            float y = Mathf.Cos(t);
            return new Vector2(x, y);
        }
        #endregion
    }
}
